home *** CD-ROM | disk | FTP | other *** search
- /* ======================================================================
-
- Function to encode and decode binary data to quoted-printable data.
- Code extracted from Eudora. Based on Steve Dorner's code.
-
- Filename: ctencode.c
- Last Edited: March 7, 1997
- Authors: Laurence Lundblade, Myra Callen
- Copyright: 1995, 1996 QUALCOMM Inc.
- Technical support: <emsapi-info@qualcomm.com>
- */
-
- #include <string.h>
- #include "ctencode.h"
- #include "rfc822.h"
-
- #define SKIP -1
- #define FAIL -2
- #define PAD -3
- #define kNewLine "\p\r\n"
- #define ABS(x) ((x)<0 ? -(x) : (x))
-
- #define kBase64CStr "base64"
- #define kQ_PCStr "quoted-printable"
- #define k7BitCStr "7bit"
- #define k8BitCStr "8bit"
- #define kBinaryCStr "binary"
-
- static UPtr gEncode =
- (UPtr) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
- static short gDecode[] =
- {
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,SKIP,SKIP,FAIL,FAIL,SKIP,FAIL,FAIL, /* 0 */
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL, /* 1 */
- SKIP,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,0x3e,FAIL,FAIL,FAIL,0x3f, /* 2 */
- 0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,FAIL,FAIL,FAIL,PAD ,FAIL,FAIL, /* 3 */
- FAIL,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e, /* 4 */
- 0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,FAIL,FAIL,FAIL,FAIL,FAIL, /* 5 */
- FAIL,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28, /* 6 */
- 0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,FAIL,FAIL,FAIL,FAIL,FAIL, /* 7 */
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL, /* 8 */
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL, /* 9 */
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL, /* A */
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL, /* B */
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL, /* C */
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL, /* D */
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL, /* E */
- FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL,FAIL, /* F */
- /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
- };
-
- /*
- * the encoding is like this:
- *
- * Binary:<111111><222222><333333>
- * 765432107654321076543210
- * Encode:<1111><2222><3333><4444>
- */
-
- /* Bit extracting macros */
- #define Bot2(b) ((b)&0x3)
- #define Bot4(b) ((b)&0xF)
- #define Bot6(b) ((b)&0x3F)
- #define Top2(b) Bot2((b)>>6)
- #define Top4(b) Bot4((b)>>4)
- #define Top6(b) Bot6((b)>>2)
-
-
- /************************************************************************
- * Encode64 - convert binary data to base64
- * binPtr -> the binary data (or nil to close encoder)
- * binLen -> the length of the binary data
- * sixFourPtr -> pointer to buffer for the base64 data
- * sixFourLen <- the length of the base64 data
- * e64 <-> state; caller must preserve
- * returns kCTEDoneOK or kCTEFailed
- ************************************************************************/
- short Encode64(const UPtr binPtr, long binLen,
- const UPtr sixFourPtr, long *sixFourBufSize, Enc64Ptr e64)
- {
- UPtr binSpot; /* the byte currently being decoded */
- UPtr sixFourSpot = sixFourPtr; /* the spot to which to copy the encoded chars */
- short bpl;
- UPtr end, bPtr; /* end of integral decoding */
- PStr newLine = kNewLine;
-
- bpl = e64->bytesOnLine; /* in inner loop; want local copy */
- bPtr = (UPtr) binPtr;
- if (binLen) {
-
- if (e64->partialCount) { // do we have any stuff left from last time?
- short needMore = 3 - e64->partialCount;
- if (binLen >= needMore) { // we can encode some bytes
- BlockMoveData(bPtr, e64->partial + e64->partialCount, needMore);
- binLen -= needMore;
- bPtr += needMore;
-
- do {
- if ((bpl) == 68) {
- int m_nl;
- for (m_nl = 0; m_nl < *(newLine); )
- *(sixFourSpot)++ = (newLine)[++m_nl];
- (bpl) = 0;
- }
- (bpl) += 4;
- *(sixFourSpot)++ = gEncode[Top6((e64->partial)[0])];
- *(sixFourSpot)++ = gEncode[Bot2((e64->partial)[0]) << 4 |
- Top4((e64->partial)[1])];
- *(sixFourSpot)++ = gEncode[Bot4((e64->partial)[1]) << 2 |
- Top2((e64->partial)[2])];
- *(sixFourSpot)++ = gEncode[Bot6((e64->partial)[2])];
- }
- while (0);
-
- if (*sixFourBufSize < sixFourSpot - sixFourPtr)
- return(kCTEFailed);
-
- e64->partialCount = 0;
- }
- /*
- * if we don't have enough bytes to complete the leftovers, we
- * obviously don't have 3 bytes. So the encoding code will fall
- * through to the point where we copy the leftovers to the partial
- * buffer. As long as we're careful to append and not copy blindly,
- * we'll be fine.
- */
- }
-
- // we encode the integral multiples of three
- end = bPtr + 3*(binLen/3);
- for (binSpot = bPtr; binSpot < end; binSpot += 3) {
- do {
- if ((bpl)==68) {
- int m_nl;
- for (m_nl=0;m_nl<*(newLine);)
- *(sixFourSpot)++ = (newLine)[++m_nl];
- (bpl) = 0;
- }
- (bpl) += 4;
- *(sixFourSpot)++ = gEncode[Top6((binSpot)[0])];
- *(sixFourSpot)++ = gEncode[Bot2((binSpot)[0])<<4 | Top4((binSpot)[1])];
- *(sixFourSpot)++ = gEncode[Bot4((binSpot)[1])<<2 | Top2((binSpot)[2])];
- *(sixFourSpot)++ = gEncode[Bot6((binSpot)[2])];
- }
- while (0);
-
- if (*sixFourBufSize < sixFourSpot - sixFourPtr)
- return(kCTEFailed);
- }
- // now, copy the leftovers to the partial buffer
- binLen = binLen % 3;
- if (binLen) {
- BlockMoveData(binSpot,e64->partial+e64->partialCount,binLen);
- e64->partialCount += binLen;
- }
- }
- else { // we've been called to cleanup the leftovers
- if (e64->partialCount) {
- if (e64->partialCount<2) e64->partial[1] = 0;
- e64->partial[2] = 0;
-
- do {
- if ((bpl)==68) {
- int m_nl;
- for (m_nl=0;m_nl<*(newLine);)
- *(sixFourSpot)++ = (newLine)[++m_nl];
- (bpl) = 0;
- }
- (bpl) += 4;
- *(sixFourSpot)++ = gEncode[Top6((e64->partial)[0])];
- *(sixFourSpot)++ = gEncode[Bot2((e64->partial)[0])<<4 | Top4((e64->partial)[1])];
- *(sixFourSpot)++ = gEncode[Bot4((e64->partial)[1])<<2 | Top2((e64->partial)[2])];
- *(sixFourSpot)++ = gEncode[Bot6((e64->partial)[2])];
- }
- while (0);
-
- if (*sixFourBufSize < sixFourSpot - sixFourPtr)
- return(kCTEFailed);
-
- // now, replace the unneeded bytes with ='s
- sixFourSpot[-1] = '=';
- if (e64->partialCount==1) sixFourSpot[-2] = '=';
- }
- }
- e64->bytesOnLine = bpl; /* copy back to state buffer */
- *sixFourBufSize = sixFourSpot - sixFourPtr; /* return actual buffer size */
-
- return kCTEDoneOK;
- }
-
-
-
- /************************************************************************
- * Decode64 - convert base64 data to binary
- * sixFourPtr -> the base64 data (or nil to close decoder)
- * sixFourLen -> the length of the base64 data
- * binPtr -> pointer to buffer to hold binary data
- * binBufSize <- length of binary data
- * d64 <-> pointer to decoder state
- * decErrCnt <- the number of decoding errors found
- * returns kCTEDoneOK or kCTEFailed
- ************************************************************************/
- short Decode64(const UPtr sixFourPtr, long sixFourLen,
- const UPtr binPtr, long *binBufSize,
- const Dec64Ptr d64, long *decErrCnt)
- {
- short decode; /* the decoded short */
- Byte c; /* the decoded byte */
- /* we separate the short & the byte so the compiler can worry about byteorder */
- UPtr end = sixFourPtr + sixFourLen; /* stop decoding here */
- UPtr binSpot = binPtr; /* current output character */
- short decoderState; /* which of 4 bytes are we seeing now? */
- long invalCount; /* how many bad chars found this time around? */
- long padCount; /* how many pad chars found so far? */
- Byte partial; /* partially decoded byte from/for last/next time */
- Boolean wasCR;
- UPtr pSixFour, sixFourStartPtr;
-
- decoderState = d64->decoderState; // fetch state from caller's buffer
- invalCount = 0; /* we'll add the invalCount to the buffer later */
- padCount = d64->padCount;
- partial = d64->partial;
- wasCR = d64->wasCR;
- pSixFour = (UPtr) sixFourPtr;
-
- if (sixFourLen) {
- sixFourStartPtr = pSixFour;
- for ( ; pSixFour < end; pSixFour++) {
- switch(decode=gDecode[*pSixFour]) {
- case SKIP: break; /* skip whitespace */
- case FAIL: invalCount++; break; /* count invalid characters */
- case PAD: padCount++; break; /* count pad characters */
- default:
- if (padCount) { // found a non-pad character
- invalCount += padCount;
- padCount = 0; // prev pad was an error
- }
- c = decode; // extract the right bits
- switch (decoderState) {
- case 0:
- partial = c<<2;
- decoderState++;
- break;
- case 1:
- *binSpot++ = partial|Top4(c);
- partial = Bot4(c)<<4;
- decoderState++;
- break;
- case 2:
- *binSpot++ = partial|Top6(c);
- partial = Bot2(c)<<6;
- decoderState++;
- break;
- case 3:
- *binSpot++ = partial|c;
- decoderState=0;
- break;
- }
- }
- /* make sure we're not writing past the end of the buffer */
- if (*binBufSize < binSpot - binPtr)
- return(kCTEFailed);
- } /* for pSixFour */
- } /* if sixFourLen */
- else { // all done. Did all end up evenly?
- switch (decoderState) {
- case 0:
- invalCount += padCount; /* came out evenly, so should be no pads */
- break;
- case 1:
- invalCount++; /* data missing */
- invalCount += padCount; /* since data missing; should be no pads */
- break;
- case 2:
- invalCount += ABS(padCount-2); /* need exactly 2 pads */
- break;
- case 3:
- invalCount += ABS(padCount-1); /* need exactly 1 pad */
- break;
- }
- }
- *binBufSize = binSpot - binPtr; /* return actual buffer size */
-
- // save state in caller's buffer
- d64->decoderState = decoderState;
- d64->invalCount += invalCount;
- d64->padCount = padCount;
- d64->partial = partial;
- d64->wasCR = wasCR;
-
- *decErrCnt = invalCount;
- return kCTEDoneOK;
- }
-
-
-
- /************************************************************************
- * EncodeQP - convert binary data to quoted-printable
- * binPtr -> the binary data
- * binLen -> the length of the binary data (or nil to close encoder)
- * qpPtr -> pointer to buffer for the quoted-printable data
- * qpBufSize <-> length of the quoted-printable buffer, returns actual length
- * eQP <-> encoder state for bytes per line; caller must preserve
- * returns kCTEDoneOK or kCTEFailed
- ************************************************************************/
- long EncodeQP(const UPtr binPtr, long binLen,
- const UPtr qpPtr, long *qpBufSize, long *eQP)
- {
- UPtr binSpot = binPtr; /* the byte currently being decoded */
- UPtr qpSpot = qpPtr; /* the spot to which to copy the encoded chars */
- UPtr binEndPtr; /* stop decoding line here */
- short bpl, c;
- static char *hex="0123456789ABCDEF";
- Boolean encode;
- UPtr nextSpace;
- PStr newLine = kNewLine;
-
- #define ROOMFOR(x) \
- do { \
- if (bpl + x > 76) { \
- *qpSpot++ = '='; \
- do { \
- BlockMoveData(newLine+1,qpSpot,*newLine); \
- qpSpot += *newLine; \
- bpl = 0; \
- } while (0); \
- } \
- } while(0)
-
- bpl = *eQP; /* in inner loop; want local copy */
- if (binLen && binPtr) {
- binEndPtr = binPtr + binLen;
-
- for (binSpot = binPtr; binSpot < binEndPtr; binSpot++) {
- /* fail if we try to write beyond the output buffer size */
- if (*qpBufSize + 4 < qpSpot - qpPtr)
- return(kCTEFailed);
-
- c = *binSpot; // make copy of char
- if (c == newLine[1]) // handle newlines
- do {
- BlockMoveData(newLine+1,qpSpot,*newLine);
- qpSpot += *newLine;
- bpl = 0;
- } while (0);
-
- else if ((c == '\n') || (c == '\r'))
- ; /* skip other newline characters */
- else {
- if ((c == ' ') || (c == '\t')) { /* trailing white space */
- encode = ((binSpot == binEndPtr - 1) || (binSpot[1] == '\r'));
- if (!encode) {
- for(nextSpace = binSpot + 1; nextSpace < binEndPtr; nextSpace++)
- if ((*nextSpace == ' ') || (*nextSpace == '\r')) {
- if ((nextSpace - binSpot) < 20) ROOMFOR(nextSpace - binSpot);
- break;
- }
- }
- }
- else
- encode = (c == '=') || (c < 33) || (c > 126); /* weird characters */
-
- if (encode)
- ROOMFOR(3);
- else
- ROOMFOR(1);
-
- encode = encode || !bpl && ((c == 'F') || (c == '.'));
-
- if (encode) {
- *qpSpot++ = '=';
- *qpSpot++ = hex[(c>>4)&0xf];
- *qpSpot++ = hex[c&0xf];
- bpl += 3;
- }
- else {
- *qpSpot++ = c;
- bpl++;
- }
- } /* else */
- } /* for */
- } /* if binLen */
- *eQP = bpl; /* copy back to state buffer */
- *qpBufSize = qpSpot - qpPtr; /* return actual buffer size */
-
- return kCTEDoneOK;
- }
-
-
- #define HEX(c) ((c) >= '0' && (c) <= '9' ? (c) - '0' : \
- ((c) >= 'A' && (c) <= 'F' ? (c) - 'A' + 10 : \
- ((c) >= 'a' && (c) <= 'f' ? (c) - 'a' + 10 : -1)))
- /************************************************************************
- * DecodeQP - convert quoted printable data to binary
- * qpStartPtr -> the quoted printable data
- * qpLen -> the length of the quoted printable data (or nil to close decoder)
- * binPtr -> pointer to buffer to hold binary data
- * binBufSize <-> the length of the binary buffer, returns actual length
- * dQP <-> pointer to decoder state; caller must preserve
- * decErrCnt <- the number of decoding errors found
- * returns kCTEDoneOK or kCTEFailed
- * Note: this does not do conversion to local newline convention as the
- * Eudora internal version does (Maybe a flag to select would be good?)
- ************************************************************************/
- long DecodeQP(const UPtr qpPtr, long qpLen,
- const UPtr binPtr, long *binBufSize,
- const DecQPPtr dQP, long *decErrCnt)
- {
- Byte c; /* the decoded byte */
- UPtr binSpot = binPtr; /* current output character */
- UPtr qpSpot = qpPtr; /* current input character */
- UPtr qpEndPtr; /* stop decoding line here */
- QPStates state;
- Byte lastChar;
- short upperNib, lowerNib, errs = 0;
- long spaceCnt, cnt;
-
- state = dQP->state; // fetch state from caller's buffer
- lastChar = dQP->lastChar;
- spaceCnt = dQP->spaceCount;
-
- if (qpLen && qpPtr) {
- qpEndPtr = qpPtr + qpLen;
- for (qpSpot = qpPtr; qpSpot < qpEndPtr; qpSpot++) {
- c = *qpSpot;
- switch (state) {
- case qpNormal:
- if (c == ' ') {
- spaceCnt++;
- }
- else if (c == '=') {
- for (cnt = 0; cnt < spaceCnt; cnt++)
- *binSpot++ = ' ';
- state = qpEqual;
- spaceCnt = 0;
- }
- else if (c == '\r' || c == '\n') {
- *binSpot++ = c;
- spaceCnt = 0; // Ignore spaces before CR
- }
- else {
- for (cnt = 0; cnt < spaceCnt; cnt++)
- *binSpot++ = ' ';
- *binSpot++ = c;
- spaceCnt = 0;
- }
- break;
-
- case qpEqual:
- if (c == ' ') {
- spaceCnt++;
- }
- else if (c == '\r' || c == '\n') {
- state = qpNormal;
- spaceCnt = 0; // Ignore spaces between Equal and CR
- }
- else
- state = qpByte1;
- break;
-
- case qpByte1:
- upperNib = HEX(lastChar);
- lowerNib = HEX(c);
- if ((upperNib < 0) || (lowerNib < 0))
- errs++;
- else
- *binSpot++ = (upperNib<<4) | lowerNib;
- state = qpNormal;
- break;
- }
- lastChar = c;
- } /* for */
- } /* if qpLen */
- else if (state != qpNormal)
- errs++; /* when closing decoder */
-
- // save state in caller's buffer
- dQP->state = state;
- dQP->lastChar = lastChar;
- dQP->spaceCount = spaceCnt;
- *binBufSize = binSpot - binPtr;
-
- *decErrCnt = errs;
- return kCTEDoneOK;
- }
-
- #pragma mark -
-
- /************************************************************************
- * RFC822_ParseCTE - parse Content-Transer-Encoding header line
- * src -> Valid Transfer-Encoding header
- * returns enumerated integer 'TrEncType' type specifying CTE.
- ************************************************************************/
- TrEncType RFC822_ParseCTE(const char *src)
- {
- const unsigned short kPrefixStrLen = strlen(kCTEncodeHdrCStr);
- char *cp = (char *) src + kPrefixStrLen, *mechanism = nil;
- TrEncType cte = CTE_Error;
-
- if (strncmp(src, kCTEncodeHdrCStr, kPrefixStrLen) == 0) { // Check prefix
- // Get first token (skips whitespace/comments)
- mechanism = RFC822_ExtractToken(&cp);
- if ((mechanism) && (strlen(mechanism) > 0)) {
- if (strcmp(mechanism, kBase64CStr) == 0)
- cte = CTE_Base64;
- else if (strcmp(mechanism, kQ_PCStr) == 0)
- cte = CTE_QP;
- else if (strcmp(mechanism, k7BitCStr) == 0)
- cte = CTE_7bit;
- else if (strcmp(mechanism, k8BitCStr) == 0)
- cte = CTE_8bit;
- else if (strcmp(mechanism, kBinaryCStr) == 0)
- cte = CTE_Binary;
- }
- DisposePtr(mechanism);
- }
- return (cte);
- }
-
-
- /************************************************************************
- * RFC822_MakeCTE - create Content-Transer-Encoding header line
- * NOTE: The user of this function is responsible for freeing the
- * returned string.
- * mechanism -> enumerated integer 'TrEncType' type specifying CTE
- * returns content tranfer encoding header line string.
- ************************************************************************/
- Handle RFC822_MakeCTE(TrEncType mechanism)
- {
- char s[80];
- Handle h;
-
- strcpy(s, kCTEncodeHdrCStr);
- strcat(s, " ");
- switch (mechanism) {
- case CTE_Base64: strcat(s, kBase64CStr); break;
- case CTE_QP: strcat(s, kQ_PCStr); break;
- case CTE_7bit: strcat(s, k7BitCStr); break;
- case CTE_8bit: strcat(s, k8BitCStr); break;
- case CTE_Binary: strcat(s, kBinaryCStr); break;
- default:
- return nil;
- }
- PtrToHand(s, &h, strlen(s));
- return h;
- }
-
-
- /************************************************************************
- * RFC822_ExtractCTE - finds and extracts the content transfer encoding
- * header line from a full multi-lined header.
- * All unfolding (removing newlines) is done before header line is returned.
- * NOTE: The user of this function is responsible for freeing the
- * returned string.
- * pFullHeader -> pointer to a full RFC822 header, including newlines
- * returns extracted header line string; dynamically allocated
- ************************************************************************/
- char *RFC822_ExtractCTE(const char *pFullHeader)
- {
- return RFC822_ExtractHeader(pFullHeader, kCTEncodeHdrCStr);
- }
-